﻿using System;
using System.Text;
using System.Web.Profile;
using log4net;
using System.Configuration;
using System.IO;
using System.Xml.Serialization;
using System.Globalization;
using System.Runtime.Serialization.Formatters.Binary;
using System.ComponentModel;

namespace Possan.MySQL.Providers
{
	public class Profile : ProfileProvider
	{
		private string _connName;
		private string _connStr;
		private string _name;
		private string _appId;
		private ILog _log;
		private UserCache _usercache;
		private ProfileCache _profilecache;

		public Profile()
		{
			_log = LogManager.GetLogger(typeof(Profile));
			_name = "";
			_connName = "";
			_appId = "";
		}

		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			_log.Debug("Initialize( name=" + name + " )");

			base.Initialize(name, config);

			_connName = "";
			if (config["connectionStringName"] != null)
				_connName = config["connectionStringName"];

			_name = name;

			_appId = "";
			if (config["applicationId"] != null)
				_appId = config["applicationId"];

			_log.Debug("_appId=" + _appId);

			_connStr = System.Web.Configuration.WebConfigurationManager.ConnectionStrings[_connName].ConnectionString;

			_usercache = new UserCache(_connStr);
			_profilecache = new ProfileCache(_connStr);
		}

		public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
		{
			_log.Debug("DeleteInactiveProfiles( authenticationOption=" + authenticationOption + ", userInactiveSinceDate=" + userInactiveSinceDate + " )");
			throw new NotImplementedException();
		}
		
		public override int DeleteProfiles(string[] usernames)
		{
			int ret = 0;
			_log.Debug("DeleteProfiles( usernames=" + string.Join(",", usernames) + " )");
			foreach(var s in usernames)
			{
				var uid = _usercache.GetIdByUsername(s);
				if (uid == "")
					continue;
				
				if (_profilecache.Delete(uid))
					ret++;
			}
			return ret;
		}

		public override int DeleteProfiles(ProfileInfoCollection profiles)
		{
			int ret = 0;
			_log.Debug("DeleteProfiles( profiles=" + profiles + " )");
			foreach (ProfileInfo s in profiles)
			{
				var uid = _usercache.GetIdByUsername(s.UserName);
				if (uid == "")
					continue;

				if (_profilecache.Delete(uid))
					ret++;
			}
			return ret;
		}

		public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
		{
			_log.Debug("FindInactiveProfilesByUserName( authenticationOption=" + authenticationOption + ", usernameToMatch=" + usernameToMatch + ", userInactiveSinceDate=" + userInactiveSinceDate + ", pageIndex=" + pageIndex + ", pageSize=" + pageSize + " )");
			throw new NotImplementedException();
		}

		public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
		{
			_log.Debug("FindProfilesByUserName( authenticationOption=" + authenticationOption + ", usernameToMatch=" + usernameToMatch + ", pageIndex=" + pageIndex + ", pageSize=" + pageSize + " )");
			throw new NotImplementedException();
		}

		public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
		{
			_log.Debug("GetAllInactiveProfiles( authenticationOption=" + authenticationOption + ", userInactiveSinceDate=" + userInactiveSinceDate + ", pageIndex=" + pageIndex + ", pageSize=" + pageSize + " )");
			throw new NotImplementedException();
		}

		public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
		{
			_log.Debug("GetAllProfiles( authenticationOption=" + authenticationOption + ", pageIndex=" + pageIndex + ", pageSize=" + pageSize + " )");
			throw new NotImplementedException();
		}

		public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
		{
			_log.Debug("GetNumberOfInactiveProfiles( authenticationOption=" + authenticationOption + ", userInactiveSinceDate=" + userInactiveSinceDate + " )");
			throw new NotImplementedException();
		}

		public override string ApplicationName
		{
			get
			{
				_log.Debug("get ApplicationName (" + _name + ")");
				return _name;
			}
			set
			{
				_log.Debug("set ApplicationName=" + value);
				_name = value;
			}
		}

		public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection properties)
		{
			_log.Debug("GetPropertyValues( context=" + context + ", properties=" + properties + " )");

			var svc = new SettingsPropertyValueCollection();
			if (properties.Count < 1)
				return svc;

			string username = (string)context["UserName"];

			_log.Debug("username=" + username);

			foreach (SettingsProperty prop in properties)
			{
				if (prop.SerializeAs == SettingsSerializeAs.ProviderSpecific)
				{
					if (prop.PropertyType.IsPrimitive || (prop.PropertyType == typeof(string)))
					{
						prop.SerializeAs = SettingsSerializeAs.String;
					}
					else
					{
						prop.SerializeAs = SettingsSerializeAs.Xml;
					}
				}
				svc.Add(new SettingsPropertyValue(prop));
			}

			if (string.IsNullOrEmpty(username))
				return svc;

			string userid = _usercache.GetIdByUsername(username);
			_log.Debug("userid=" + userid);

			ProfileCacheData finalprop = _profilecache.Get(userid);
			if (finalprop == null)
				return svc;

			_log.Debug("propnames=" + finalprop.Names);
			_log.Debug("propdata=" + finalprop.Data);

			string[] namelist = finalprop.Names.Split(new [] { ':' });
			if (namelist.Length > 0)
				ParseDataFromDB(namelist, finalprop.Data, new byte[0], svc);

			return svc;
		}

		public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection properties)
		{
			_log.Debug("SetPropertyValues( context=" + context + ", collection=" + properties + " )");

			if (properties.Count < 1)
				return;

			string username = (string)context["UserName"];
			string userid = _usercache.GetIdByUsername(username);

			string propnames = "";
			string propdata = "";
			byte[] binary = null;

			PrepareDataForSaving(ref propnames, ref propdata, ref binary, false, properties);

			_log.Debug("propnames=" + propnames);
			_log.Debug("propdata=" + propdata);

			_profilecache.Save(userid, new ProfileCacheData { Names = propnames, Data = propdata });
		}

		#region ASPNET Magic Serialization Junk (thank you reflector)

		private static string ConvertObjectToString(object propValue, Type type, SettingsSerializeAs serializeAs, bool throwOnError)
		{
			if (serializeAs == SettingsSerializeAs.ProviderSpecific)
			{
				if ((type == typeof(string)) || type.IsPrimitive)
				{
					serializeAs = SettingsSerializeAs.String;
				}
				else
				{
					serializeAs = SettingsSerializeAs.Xml;
				}
			}
			try
			{
				switch (serializeAs)
				{
					case SettingsSerializeAs.String:
						{
							TypeConverter converter = TypeDescriptor.GetConverter(type);
							if (((converter == null) || !converter.CanConvertTo(typeof(string))) || !converter.CanConvertFrom(typeof(string)))
							{
								throw new ArgumentException("Unable to convert type " + type.ToString() + " to string", "type");
							}
							return converter.ConvertToString(propValue);
						}
					case SettingsSerializeAs.Xml:
						break;

					case SettingsSerializeAs.Binary:
						{
							MemoryStream ms = new MemoryStream();
							try
							{
								new BinaryFormatter().Serialize(ms, propValue);
								return Convert.ToBase64String(ms.ToArray());
							}
							finally
							{
								ms.Close();
							}
							break;
						}
					default:
						goto Label_00F7;
				}
				XmlSerializer xs = new XmlSerializer(type);
				StringWriter sw = new StringWriter(CultureInfo.InvariantCulture);
				xs.Serialize((TextWriter)sw, propValue);
				return sw.ToString();
			}
			catch (Exception e)
			{
				if (throwOnError)
				{
					throw;
				}
			}
		Label_00F7:
			return null;
		}

		private static object SerializePropertyValue(SettingsPropertyValue prop)
		{
			object obj;
			object val = prop.PropertyValue;
			if (val == null)
			{
				return null;
			}
			if (prop.Property.SerializeAs != SettingsSerializeAs.Binary)
			{
				return ConvertObjectToString(val, prop.Property.PropertyType, prop.Property.SerializeAs, prop.Property.ThrowOnErrorSerializing);
			}
			var ms = new MemoryStream();
			try
			{
				new BinaryFormatter().Serialize(ms, val);
				obj = ms.ToArray();
			}
			finally
			{
				ms.Close();
			}
			return obj;
		}

		private static void PrepareDataForSaving(ref string allNames, ref string allValues, ref byte[] buf, bool binarySupported, SettingsPropertyValueCollection properties/*, bool userIsAuthenticated*/)
		{
			var names = new StringBuilder();
			var values = new StringBuilder();
			MemoryStream ms = binarySupported ? new MemoryStream() : null;

			try
			{
				try
				{
					bool anyItemsToSave = false;
					foreach (SettingsPropertyValue pp in properties)
					{
						if (pp.IsDirty)// && (userIsAuthenticated || ((bool)pp.Property.Attributes["AllowAnonymous"])))
						{
							anyItemsToSave = true;
							break;
						}
					}

					if (!anyItemsToSave)
					{
						return;
					}

					foreach (SettingsPropertyValue pp in properties)
					{
						//	if ((!pp.IsDirty && pp.UsingDefaultValue))	
						//		continue;

						int len = 0;
						int startPos = 0;
						string propValue = null;
						if (pp.Deserialized && (pp.PropertyValue == null))
						{
							len = -1;
						}
						else
						{
							object sVal = SerializePropertyValue(pp);
							if (sVal == null)
							{
								len = -1;
							}
							else
							{
								if (!(sVal is string) && !binarySupported)
								{
									sVal = Convert.ToBase64String((byte[])sVal);
								}
								if (sVal is string)
								{
									propValue = (string)sVal;
									len = propValue.Length;
									startPos = values.Length;
								}
								else
								{
									byte[] b2 = (byte[])sVal;
									startPos = (int)ms.Position;
									ms.Write(b2, 0, b2.Length);
									ms.Position = startPos + b2.Length;
									len = b2.Length;
								}
							}
						}
						names.Append(pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" + startPos.ToString(CultureInfo.InvariantCulture) + ":" + len.ToString(CultureInfo.InvariantCulture) + ":");
						if (propValue != null)
						{
							values.Append(propValue);
						}
					}
					if (binarySupported)
					{
						buf = ms.ToArray();
					}
				}
				finally
				{
					if (ms != null)
					{
						ms.Close();
					}
				}
			}
			catch (Exception e)
			{
				throw e;
			}
			allNames = names.ToString();
			allValues = values.ToString();
		}

		private static object GetObjectFromString(Type type, SettingsSerializeAs serializeAs, string attValue)
		{
			StringReader sr;
			if ((type == typeof(string)) && (((attValue == null) || (attValue.Length < 1)) || (serializeAs == SettingsSerializeAs.String)))
			{
				return attValue;
			}
			if ((attValue != null) && (attValue.Length >= 1))
			{
				switch (serializeAs)
				{
					case SettingsSerializeAs.String:
						{
							TypeConverter converter = TypeDescriptor.GetConverter(type);
							if (((converter == null) || !converter.CanConvertTo(typeof(string))) || !converter.CanConvertFrom(typeof(string)))
							{
								throw new ArgumentException("Unable to convert type: " + type.ToString() + " from string", "type");
							}
							return converter.ConvertFromString(attValue);
						}
					case SettingsSerializeAs.Xml:
						goto Label_0070;

					case SettingsSerializeAs.Binary:
						{
							byte[] buf = Convert.FromBase64String(attValue);
							MemoryStream ms = null;
							try
							{
								ms = new MemoryStream(buf);
								return new BinaryFormatter().Deserialize(ms);
							}
							finally
							{
								if (ms != null)
								{
									ms.Close();
								}
							}
							goto Label_0070;
						}
				}
			}
			return null;
		Label_0070:
			sr = new StringReader(attValue);
			XmlSerializer xs = new XmlSerializer(type);
			return xs.Deserialize(sr);
		}

		private object Deserialize(SettingsPropertyValue prop, object obj)
		{
			object val = null;
			if (obj != null)
			{
				try
				{
					if (obj is string)
					{
						val = GetObjectFromString(prop.Property.PropertyType, prop.Property.SerializeAs, (string)obj);
					}
					else
					{
						var ms = new MemoryStream((byte[])obj);
						try
						{
							val = new BinaryFormatter().Deserialize(ms);
						}
						finally
						{
							ms.Close();
						}
					}
				}
				catch
				{
				}
				if ((val != null) && !prop.Property.PropertyType.IsAssignableFrom(val.GetType()))
				{
					val = null;
				}
			}
			if (val == null)
			{
				if ((prop.Property.DefaultValue == null) || (prop.Property.DefaultValue.ToString() == "[null]"))
				{
					if (prop.Property.PropertyType.IsValueType)
					{
						return Activator.CreateInstance(prop.Property.PropertyType);
					}
					return null;
				}
				if (prop.Property.DefaultValue is string)
				{
					try
					{
						val = GetObjectFromString(prop.Property.PropertyType, prop.Property.SerializeAs, (string)prop.Property.DefaultValue);
					}
					catch (Exception e)
					{
						_log.Debug(val);
					}
				}
				else
				{
					val = prop.Property.DefaultValue;
				}
				if ((val != null) && !prop.Property.PropertyType.IsAssignableFrom(val.GetType()))
				{
					throw new ArgumentException("Could not create from default value for property: " + prop.Property.Name);
				}
			}
			if (val == null)
			{
				if (prop.Property.PropertyType == typeof(string))
				{
					return "";
				}
				try
				{
					val = Activator.CreateInstance(prop.Property.PropertyType);
				}
				catch
				{
				}
			}
			return val;
		}

		private void ParseDataFromDB(string[] names, string values, byte[] buf, SettingsPropertyValueCollection properties)
		{
			if (((names != null) && (values != null)) && ((buf != null) && (properties != null)))
			{
				try
				{
					for (int iter = 0; iter < (names.Length / 4); iter++)
					{
						string name = names[iter * 4];
						SettingsPropertyValue pp = properties[name];
						if (pp != null)
						{
							int startPos = int.Parse(names[(iter * 4) + 2], CultureInfo.InvariantCulture);
							int length = int.Parse(names[(iter * 4) + 3], CultureInfo.InvariantCulture);
							if ((length == -1) && !pp.Property.PropertyType.IsValueType)
							{
								pp.PropertyValue = null;
								pp.IsDirty = false;
								pp.Deserialized = true;
							}
							if (((names[(iter * 4) + 1] == "S") && (startPos >= 0)) && ((length > 0) && (values.Length >= (startPos + length))))
							{
								pp.PropertyValue = this.Deserialize(pp, values.Substring(startPos, length));
							}
							if (((names[(iter * 4) + 1] == "B") && (startPos >= 0)) && ((length > 0) && (buf.Length >= (startPos + length))))
							{
								byte[] buf2 = new byte[length];
								Buffer.BlockCopy(buf, startPos, buf2, 0, length);
								pp.PropertyValue = this.Deserialize(pp, buf2);
							}
						}
					}
				}
				catch
				{
				}
			}
		}

		#endregion

	}
}
